package edu.pku.sei.mdabase.infrastructure.collection;

import java.util.ArrayList;
import java.util.Collection;

import edu.pku.sei.mdabase.infrastructure.metamodel.MetaClass;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaReference;
import edu.pku.sei.mdabase.infrastructure.model.MClass;
import edu.pku.sei.mdabase.infrastructure.model.MModelGraph;
import edu.pku.sei.mdabase.infrastructure.model.MModelObject;
import edu.pku.sei.mdabase.infrastructure.model.MReference;

public class OclCollection extends ArrayList<Object> {
	private static final long serialVersionUID = 8480495426159664084L;
	/*private DataType dataType = null; 
	public DataType getElementType(){
		return dataType;
	}
	public void setDataType(DataType dataType) {
		this.dataType = dataType;
	}*/
	
	protected boolean isOrdered;
	protected boolean isUnique;
	
	public boolean isOrdered() {
		return isOrdered;
	}
	public boolean isUnicity() {
		return isUnique;
	}
	
	static public OclCollection createReferenceCollection(MClass hostClass,MetaReference refType,Object key){
		ReferenceCollection col = new ReferenceCollection();
		col.isOrdered = true;
		col.isUnique = true;
		
		col.setHost(hostClass);
		col.setReferenceType(refType);
		col.setKey(key);
		
		return col;
	}
	
	static public void resetCollection(OclCollection col){
		if(col instanceof ReferenceCollection){
			col.clear();
		} else {
			col.clear();
		}
	}
	
	static public void simpleAdd(OclCollection col,Object o){
		if(col instanceof ReferenceCollection){
			((ReferenceCollection)col).simpleAdd(o);
		} else {
			col.add(o);
		}
	}
	
	static public OclCollection createOclBag(){
		OclCollection col = new OclCollection();
		col.isOrdered = false;
		col.isUnique = false;
		//col.dataType = type;
		return col;
	}
	static public OclCollection createOclSet(){
		OclCollection col = new OclCollection();
		col.isOrdered = false;
		col.isUnique = true;
		//col.dataType = type;
		return col;
	}
	static public OclCollection createOclOrderedSet(){
		OclCollection col = new OclCollection();
		col.isOrdered = true;
		col.isUnique = true;
		//col.dataType = type;
		return col;
	}
	static public OclCollection createOclSequence(){
		OclCollection col = new OclCollection();
		col.isOrdered = true;
		col.isUnique = false;
		//col.dataType = type;
		return col;
	}
	
	@Override
	public void add(int index, Object element) {
		if(isUnique&&contains(element))
			return;
		super.add(index, element);
	}
	
	

	@Override
	public boolean add(Object e) {
		if(isUnique&&contains(e))
			return false;		
		boolean flag= super.add(e);
		return flag;
	}

	@Override
	public boolean addAll(Collection<? extends Object> c) {
		if(isUnique){
			for(Object o : c){
				add(o);
			}
			return true;
		} else {
			for(Object e : c){
				add(e);
			}
			return true;
		}
	}

	@Override
	public boolean addAll(int index, Collection<? extends Object> c) {
		if(isUnique){
			int i = index;
			int size = 0;
			for(Object o : c){
				size = this.size();
				add(i,o);
				if(size!=this.size())
					i++;
			}
			return i!=index;
		} else {
			for(Object e : c){
				add(index,e);
				index++;
			}
			return true;
		}
	}
	
	public int getOrder(Object o){
		return indexOf(o);
	}
	public boolean equals(Object o){
		if(o instanceof Collection)
			return super.equals(o);
		else {
			if(this.size()==1){
				return this.get(0).equals(o);
			} else
				return false;
		}
	}
}

class ReferenceCollection extends OclCollection { // a collection of reference owned by a MClass

	private MClass hostClass;
	private MetaReference referenceType;
	private Object key;

	public void setHost(MClass host) {
		this.hostClass = host;
	}

	public void simpleAdd(Object o) {
		super.add(o);
	}

	public void setReferenceType(MetaReference referenceType) {
		this.referenceType = referenceType;
	}

	public void setKey(Object key) {
		this.key = key;
	}

	protected void updateHost(MClass o, boolean add) {
		if(hostClass!=null){
			MModelGraph hostModel = hostClass.getHost(key);
			if(add){
				hostModel.addNode(o);// if o is contained, nothing will happen
				MReference ref = new MReference(); // new reference
				ref.setSource(hostClass);
				ref.setTarget(o);
				ref.setType(referenceType);
				hostModel.addEdge(ref);
			}  else {
				if(referenceType.isComposite())
					hostModel.removeNode(o);//when deleting the node, all the reference 
				else {
					MReference ref=hostModel.findEdge(hostClass,o, referenceType);
					hostModel.removeEdge(ref);
				}
			}
				
		}
	}

	@Override
	public void add(int index, Object element) {		
		int size = this.size();
		super.add(index, element);
		if(size!=this.size())
			updateHost((MClass)element, true);
		
		assert (!referenceType.isMultiple())&&size()>1;
	}

	@Override
	public boolean add(Object e) {
		// TODO Auto-generated method stub
		if( super.add(e) ) {
			updateHost((MClass)e, true);
			return true;
		}
		return false;
	}

	@Override
	public Object remove(int index) {
		Object e = super.remove(index);
		if(e!=null){
			updateHost((MClass)e,false);
		}
		return e;
	}

	@Override
	public boolean remove(Object e) {
		// TODO Auto-generated method stub
		boolean flag = super.remove(e);
		if(flag){
			updateHost((MClass)e,false);
		}
		
		return flag;
	}
	
}
